import re
import socket
import threading
import time
from typing import Callable, Optional
import enum


class ClientEvent(enum.Enum):
    """网络事件"""
    StartConnect = 0
    """开始连接"""
    Connected = 1
    """连接成功"""
    ConnectFailed = 2
    """连接失败"""
    Disconnected = 3
    """连接断开"""

class ServerEvent(enum.Enum):
    """服务器事件"""
    StartListen = 0
    """开始监听"""
    NewSession = 1
    """新会话"""
    SessionDone = 2
    """会话结束"""
    StopListen = 3
    """结束监听"""


def is_host_valid(host: str) -> bool:
    """
    主机地址是否有效（仅检查语法）
    :param host: 主机地址
    :return: 是否有效
    """
    # 正则表达式匹配IPv4地址
    ipv4_pattern = r'^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'
    # 正则表达式匹配IPv6地址
    ipv6_pattern = r'^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$'
    # 正则表达式匹配DNS域名
    dns_pattern = r'^(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,6}|localhost)$'

    # 检查IPv4
    if re.match(ipv4_pattern, host):
        return True
    # 检查IPv6
    elif re.match(ipv6_pattern, host):
        return True
    # 检查DNS
    elif re.match(dns_pattern, host):
        return True
    else:
        return False


def is_port_valid(port: int) -> bool:
    """
    端口是否有效（仅检查语法）
    :param port: 端口
    :return: 是否有效
    """
    if 0 <= port <= 65535:
        return True
    else:
        return False


SessionHandle = tuple[str, int]


class NetEngine:
    def __init__(self):
        """
        联网引擎，内部会新建线程，用作socket相关操作
        Examples:
            engine = NetEngine()
            engine.
        """
        self._socket: Optional[socket.socket] = None
        self._thread_socket_run = threading.Thread(target=self._socket_run, daemon=True)

        self._net_event_notify_list: list[Callable[[ClientEvent, str, int], None]] = []
        self._data_notify_list: list[Callable[[bool, bytes], None]] = []
        self._send_list: list[bytes] = []
        self._send_list_lock = threading.Lock()

        self._is_start_connect = False
        self._is_disconnect = False
        self.is_net_connected = False
        self._remote_host: Optional[str] = None
        self._remote_port: Optional[int] = None

        self._cfg_auto_reconnect: bool = False
        self._cfg_connect_timeout: float = 3
        self._cfg_reconnect_intv: float = 5

        self._thread_socket_run.start()

    def _net_event_notify(self, event: ClientEvent, host: str, port: int) -> None:
        for callback in self._net_event_notify_list:
            callback(event, host, port)

    def _data_notify(self, is_recv, data: bytes) -> None:
        for callback in self._data_notify_list:
            callback(is_recv, data)

    def _socket_run(self) -> None:
        class _SocketRunState(enum.Enum):
            Idle = 0
            """空闲"""
            StartConnect = 1
            """开始连接"""
            WaitConnectTimeout = 2
            """连接超时"""
            Connected = 3
            """已连接"""
            StartDisConnect = 4
            """开始断开连接"""
            DisConnected = 5
            """已断开连接"""
        _run_state: _SocketRunState = _SocketRunState.Idle
        connect_fail_time = 0
        while True:
            if _run_state == _SocketRunState.Idle:
                """空闲"""
                if self._is_start_connect:
                    _run_state = _SocketRunState.StartConnect
                    self._is_start_connect = False
            elif _run_state == _SocketRunState.StartConnect:
                """开始连接"""
                if self._remote_host is not None and self._remote_port is not None:
                    self._net_event_notify(ClientEvent.StartConnect, self._remote_host, self._remote_port)
                    self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                    self._socket.settimeout(self._cfg_connect_timeout)
                    try:
                        self._socket.connect((self._remote_host, self._remote_port))
                    # except socket.timeout:
                    #     self._socket.close()
                    #     self._net_event_notify(ClientEvent.ConnectFailed, self._remote_host, self._remote_port)
                    #     connect_fail_time = time.time()
                    #     _run_state = _SocketRunState.WaitConnectTimeout
                    # except ConnectionRefusedError:
                    #     self._socket.close()
                    #     self._net_event_notify(ClientEvent.ConnectFailed, self._remote_host, self._remote_port)
                    #     connect_fail_time = time.time()
                    #     _run_state = _SocketRunState.WaitConnectTimeout
                    except Exception as other_error:
                        self._socket.close()
                        self._net_event_notify(ClientEvent.ConnectFailed, self._remote_host, self._remote_port)
                        connect_fail_time = time.time()
                        _run_state = _SocketRunState.WaitConnectTimeout
                    else:
                        self._socket.settimeout(0.5)  # 连接成功，设置接收超时需要短一点
                        self.is_net_connected = True
                        self._net_event_notify(ClientEvent.Connected, self._remote_host, self._remote_port)
                        _run_state = _SocketRunState.Connected
                else:
                    self._net_event_notify(ClientEvent.ConnectFailed, "None", 0)
                    _run_state = _SocketRunState.Idle
            elif _run_state == _SocketRunState.WaitConnectTimeout:
                """连接超时"""
                if self._cfg_auto_reconnect:
                    if time.time() - connect_fail_time >= self._cfg_reconnect_intv:
                        _run_state = _SocketRunState.StartConnect
                else:
                    _run_state = _SocketRunState.Idle
            elif _run_state == _SocketRunState.Connected:
                """已连接"""
                # 是否需要断开
                if self._is_disconnect:
                    self.is_net_connected = False
                    _run_state = _SocketRunState.StartDisConnect
                    self._is_disconnect = False
                    continue
                # 发送
                self._send_list_lock.acquire()
                if len(self._send_list) > 0:
                    data = self._send_list.pop(0)
                    try:
                        self._socket.sendall(data)
                    # except socket.timeout:
                    #     self.is_net_connected = False
                    #     _run_state = _SocketRunState.StartDisConnect
                    #     self._send_list_lock.release()
                    #     continue
                    except Exception as other_error:
                        self.is_net_connected = False
                        _run_state = _SocketRunState.StartDisConnect
                        self._send_list_lock.release()
                        continue
                    else:
                        self._data_notify(False, data)
                self._send_list_lock.release()
                # 接收
                try:
                    data = self._socket.recv(1024)
                except socket.timeout:
                    pass
                # except ConnectionResetError:
                #     self.is_net_connected = False
                #     _run_state = _SocketRunState.StartDisConnect
                except Exception as other_error:
                    self.is_net_connected = False
                    _run_state = _SocketRunState.StartDisConnect
                else:
                    if len(data) > 0:
                        self._data_notify(True, data)
                    else:  # 连接被远程关闭
                        self.is_net_connected = False
                        _run_state = _SocketRunState.StartDisConnect
            elif _run_state == _SocketRunState.StartDisConnect:
                """开始断开连接"""
                self._socket.close()
                _run_state = _SocketRunState.DisConnected
            elif _run_state == _SocketRunState.DisConnected:
                """已断开连接"""
                self._net_event_notify(ClientEvent.Disconnected, self._remote_host, self._remote_port)
                _run_state = _SocketRunState.Idle
            time.sleep(0.05)

    def set_param(self, auto_reconnect: bool = False, connect_timeout: float = 3, reconnect_intv: float = 5) -> None:
        """
        设置引擎参数
        :param auto_reconnect: 是否自动重连，连接失败时判断，连接断开时不判断
        :param connect_timeout: 连接超时时间（单位：秒）
        :param reconnect_intv: 重连间隔（单位：秒）
        :return: None
        """
        if connect_timeout > 0:
            self._cfg_auto_reconnect = auto_reconnect
            self._cfg_connect_timeout = connect_timeout
            self._cfg_reconnect_intv = reconnect_intv
        else:
            raise ValueError('连接超时时间需大于0')

    def register_data_notify(self, notify_callback: Callable[[bool, bytes], None]) -> None:
        """
        注册数据回调，支持多次注册，内部会检查唯一性
        :param notify_callback: 数据回调函数，参数：（用户指针，是否是接收数据，数据）
        :return: None
        """
        if not notify_callback in self._data_notify_list:
            self._data_notify_list.append(notify_callback)

    def register_client_event_notify(self, notify_callback: Callable[[ClientEvent, str, int], None]) -> None:
        """
        注册客户端事件回调，支持多次注册，内部会检查唯一性
        :param notify_callback: 联网事件回调函数，参数：（用户指针，客户端事件(详见：ClientEvent)，连接的IP，连接的端口）
        :return: None
        """
        if not notify_callback in self._net_event_notify_list:
            self._net_event_notify_list.append(notify_callback)

    def start_connect(self, host: str, port: int) -> None:
        """
        启动连接，若已连接或正在连接，则下次断开连接后会自动重连
        :param host: 远端主机
        :param port: 远端端口
        :return: None
        """
        if is_host_valid(host) and is_port_valid(port):
            self._remote_host = host
            self._remote_port = port
            self._is_start_connect = True
        else:
            raise ValueError("主机地址不合法")

    def disconnect(self) -> None:
        """
        断开连接，若未连接，则连接成功后会自动断开
        :return: None
        """
        self._is_disconnect = True

    def send(self, data: bytes) -> None:
        """
        发送数据，仅在网络已连接(self.is_net_connected == True)时调用有效，内部含有互斥锁，可能会阻塞一下下
        :param data: 欲发送的数据
        :return: None
        """
        if self.is_net_connected and len(data) > 0:
            self._send_list_lock.acquire()
            self._send_list.append(data)
            self._send_list_lock.release()


class NetEngineServer:
    def __init__(self):
        """
        联网引擎(服务器端)，内部会新建大量线程，用作socket相关操作
        Examples:
            engine = NetEngineServer()
            engine.
        """
        self._socket: Optional[socket.socket] = None
        self._thread_socket_run = threading.Thread(target=self._server_run, daemon=True)
        self.session_dir: dict[SessionHandle, _Session] = {}
        self.use_port = 0

        self._server_event_notify_list: list[Callable[[Optional[SessionHandle], ServerEvent], None]] = []
        self._data_notify_list: list[Callable[[SessionHandle, bool, bytes], None]] = []

        self._is_start_listen = False
        self._is_stop_listen = False

        self._thread_socket_run.start()

    def _server_event_notify(self, session_p: Optional[SessionHandle], event: ServerEvent) -> None:
        if event == ServerEvent.SessionDone:  # 如果来的是会话结束事件，回收这个会话
            session = self.session_dir.pop(session_p, None)
            if session is not None:
                del session
        for callback in self._server_event_notify_list:
            callback(session_p, event)

    def _data_notify(self, session_p: SessionHandle, is_recv: bool, data: bytes) -> None:
        for callback in self._data_notify_list:
            callback(session_p, is_recv, data)

    def _server_run(self) -> None:
        class _SocketRunState(enum.Enum):
            Idle = 0
            """空闲"""
            StartListen = 1
            """开始监听"""
            StartStopListen = 2
            """开始停止监听"""
            WaitSessionDisConnect = 3
            """等待会话断开连接"""
            StopListened = 4
            """已停止监听"""
        _run_state: _SocketRunState = _SocketRunState.Idle
        while True:
            if _run_state == _SocketRunState.Idle:
                """空闲"""
                if self._is_start_listen:
                    self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                    self._socket.settimeout(0.5)
                    self._socket.bind(('0.0.0.0', self.use_port))
                    self._socket.listen()
                    _run_state = _SocketRunState.StartListen
                    self._server_event_notify(None, ServerEvent.StartListen)
                    self._is_start_listen = False
            elif _run_state == _SocketRunState.StartListen:
                """开始监听"""
                if self._is_stop_listen:
                    _run_state = _SocketRunState.StartStopListen
                    self._is_stop_listen = False
                try:
                    client_socket, address = self._socket.accept()
                except socket.timeout:
                    pass
                except Exception as other_error:
                    self._socket.close()
                    self._is_start_listen = True
                    _run_state = _SocketRunState.Idle
                else:
                    self.session_dir[address] = _Session(self, client_socket, address)
            elif _run_state == _SocketRunState.StartStopListen:
                """开始停止监听"""
                for session in self.session_dir.values():
                    session.disconnect()
                _run_state = _SocketRunState.WaitSessionDisConnect
            elif _run_state == _SocketRunState.WaitSessionDisConnect:
                if len(self.session_dir) == 0:
                    _run_state = _SocketRunState.StopListened
            elif _run_state == _SocketRunState.StopListened:
                """已停止监听"""
                self._socket.close()
                self._server_event_notify(None, ServerEvent.StopListen)
                _run_state = _SocketRunState.Idle
            if _run_state != _SocketRunState.StartListen:
                time.sleep(0.05)

    def register_data_notify(self, notify_callback: Callable[[SessionHandle, bool, bytes], None]) -> None:
        """
        注册数据回调，支持多次注册，内部会检查唯一性
        :param notify_callback: 数据回调函数，参数：（用户指针，是否是接收数据，数据）
        :return: None
        """
        if not notify_callback in self._data_notify_list:
            self._data_notify_list.append(notify_callback)

    def register_server_event_notify(self, notify_callback: Callable[[SessionHandle, ServerEvent], None]) -> None:
        """
        注册服务器事件回调，支持多次注册，内部会检查唯一性
        :param notify_callback: 联网事件回调函数，参数：（用户指针，服务器事件(详见：ServerEvent)，连接的IP，连接的端口）
        :return: None
        """
        if not notify_callback in self._server_event_notify_list:
            self._server_event_notify_list.append(notify_callback)

    def start_listen(self, port: int) -> None:
        """
        开始监听
        :param port: 监听的端口
        :return: None
        """
        if is_port_valid(port):
            self.use_port = port
            self._is_start_listen = True
        else:
            raise ValueError("监听端口不合法")

    def disconnect_session(self, session_p: SessionHandle) -> None:
        """
        断开一个会话
        :param session_p: 会话句柄
        :return: None
        """
        session = self.session_dir.get(session_p)
        if session is not None:
            session.disconnect()
        else:
            self._server_event_notify(session_p, ServerEvent.SessionDone)

    def stop_listen(self) -> None:
        """
        停止监听
        :return: None
        """
        self._is_stop_listen = True

    def send(self, session_p: SessionHandle, data: bytes) -> None:
        """
        发送数据，仅在会话还在时有效，内部含有互斥锁，可能会阻塞一下下
        :param session_p 会话句柄
        :param data: 欲发送的数据
        :return: None
        """
        session = self.session_dir.get(session_p)
        if session_p is not None:
            session.send(data)


class _Session(threading.Thread):
    def __init__(self, engine: NetEngineServer, socket_p: socket.socket, session_p: SessionHandle):
        """
        会话类，服务器使用，一个会话代表一个tcp连接，同时也是一个线程
        :param engine: 所属的服务器引擎
        :param socket_p: socket句柄
        :param session_p: 会话句柄
        """
        self.engine = engine
        self._socket = socket_p
        self._session_p = session_p
        self._send_list: list[bytes] = []
        self._send_list_lock = threading.Lock()
        self._is_disconnect = False
        self._is_run = True
        super().__init__(target=self._session_run, daemon=True)
        self.start()

    def _session_run(self):
        class _SocketRunState(enum.Enum):
            Connected = 0
            """已连接"""
            StartDisConnect = 2
            """开始断开连接"""
            DisConnected = 3
            """已断开连接"""
        _run_state: _SocketRunState = _SocketRunState.Connected
        self.engine._server_event_notify(self._session_p, ServerEvent.NewSession)
        self._socket.settimeout(0.5)
        while self._is_run:
            if _run_state == _SocketRunState.Connected:
                """已连接"""
                # 是否需要断开
                if self._is_disconnect:
                    self.is_net_connected = False
                    _run_state = _SocketRunState.StartDisConnect
                    self._is_disconnect = False
                    continue
                # 发送
                self._send_list_lock.acquire()
                if len(self._send_list) > 0:
                    data = self._send_list.pop(0)
                    try:
                        self._socket.sendall(data)
                    # except socket.timeout:
                    #     self.is_net_connected = False
                    #     _run_state = _SocketRunState.StartDisConnect
                    #     self._send_list_lock.release()
                    #     continue
                    except Exception as other_error:
                        self.is_net_connected = False
                        _run_state = _SocketRunState.StartDisConnect
                        self._send_list_lock.release()
                        continue
                    else:
                        self.engine._data_notify(self._session_p, False, data)
                self._send_list_lock.release()
                # 接收
                try:
                    data = self._socket.recv(1024)
                except socket.timeout:
                    pass
                # except ConnectionResetError:
                #     self.is_net_connected = False
                #     _run_state = _SocketRunState.StartDisConnect
                except Exception as other_error:
                    self.is_net_connected = False
                    _run_state = _SocketRunState.StartDisConnect
                else:
                    if len(data) > 0:
                        self.engine._data_notify(self._session_p, True, data)
                    else:  # 连接被远程关闭
                        self.is_net_connected = False
                        _run_state = _SocketRunState.StartDisConnect
            elif _run_state == _SocketRunState.StartDisConnect:
                """开始断开连接"""
                self._socket.close()
                self.engine._server_event_notify(self._session_p, ServerEvent.SessionDone)
                _run_state = _SocketRunState.DisConnected
            elif _run_state == _SocketRunState.DisConnected:
                """已断开连接"""
                return
            if _run_state != _SocketRunState.Connected:
                time.sleep(0.05)

    def disconnect(self) -> None:
        """
        断开会话
        :return: None
        """
        self._is_disconnect = True

    def send(self, data: bytes) -> None:
        """
        发送数据
        :param data: 欲发送的数据
        :return: None
        """
        self._send_list_lock.acquire()
        self._send_list.append(data)
        self._send_list_lock.release()
